การเขียน Lambda ใน Custom widget บน CloudWatch dashboard
บทความนี้แปลมาจากบทความที่เป็นภาษาญี่ปุ่นที่ชื่อว่า CloudWatchのカスタムウィジェットのLambda書いてみた โดยเจ้าของบทความนี้คือ คุณ eetann (えーたんさん) เป็นคนญี่ปุ่นครับ
เมื่อแปลจากภาษาญี่ปุ่นมาเป็นภาษาไทยแล้วผมได้เรียบเรียงเนื้อหาใหม่เพื่อให้เข้าใจง่ายขึ้น เนื่องจากบางครั้งอาจมีการอัปเดตข้อมูลใหม่ จึงมีความจำเป็นต้องอัปเดตให้เป็นข้อมูลปัจจุบัน
วันก่อนผมได้รู้ว่าสามารถแสดงผลลัพธ์การดำเนินการของ Lambda เป็น Custom widget ใน CloudWatch dashboard ได้ ผมจึงได้ศึกษาและทดลองการใช้งานมาแล้ว ครั้งนี้จึงอยากจะมาสร้าง widget ที่เปลี่ยนเนื้อหาการแสดงตามปุ่มที่คลิก
หน้าจอ Confirmation จะปรากฏขึ้นมาเมื่อทำการคลิกปุ่ม
สิ่งที่ต้องเตรียม
※EC2 Instance Amazon Linux 2023 ที่ติดตั้ง CDK TypeScript project (custom-widget-lambda-sample) (หรือหากมีสภาพแวดล้มอื่นก็สามารถใช้ได้เช่นกัน)
ดูตัวอย่างได้ที่ลิงก์ด้านล่างนี้โดยอ่านหมายเหตุก่อนเริ่มดำเนินการ
หมายเหตุ: สำหรับขั้นตอนของหัวข้อด้านล่างนี้ยังไม่ต้องทำ เนื่องจากต้องแก้ไขไฟล์บางส่วนให้เป็นภาษาไทยก่อน แล้วจึงดำเนินการ Deploy โปรเจกต์ตามขั้นตอนด้านล่างนี้ และเพื่อให้ง่ายต่อการดำเนินการ ผมจะเขียนลิงก์ไว้ให้ในหัวข้อ Deploy CDK ไปยัง Lambda อีกทีครับ
- ❌ การ Deploy CDK ไปยัง Lambda (ทำภายหลัง)
- ❌ ตรวจสอบ CDK TypeScript project ใน Lambda (ทำภายหลัง)
เมื่อเตรียมสภาพแวดล้อมเสร็จแล้วให้เริ่มทำขั้นตอนถัดไปได้เลยครับ
แก้ไข Code ในไฟล์
รายละเอียดเกี่ยวกับไฟล์มีดังนี้
- ไฟล์
lib/custom-widget-lambda-sample-stack.ts
(ไม่ต้องทำการแก้ไข) - ไฟล์
lambda/custom-widget-function.ts
(ให้แก้ไขโดยคลิกขยายเพื่อดู Codeและคัดลอกโค้ดนี้ไปวางแทนที่โค้ดเก่าทั้งหมด เนื่องจากไฟล์เดิมมีเนื้อหาบางส่วนเป็นภาษาญี่ปุ่น)
ครั้งนี้เป็นการสร้าง Lambda โดยใช้ NodejsFunction บน CDK
lib/custom-widget-lambda-sample-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
export class CustomWidgetLambdaSampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
new NodejsFunction(this, "CustomWidgetLambda", {
entry: "lambda/custom-widget-function.ts",
});
}
}
ต่อไปนี้เป็นการเขียนโดยละเว้นบางส่วนเพื่อให้เข้าใจกระบวนการทั้งหมดที่เขียนใน Lambda ครับ
lambda/custom-widget-function.ts
const DOCS = `
สามารถเขียน Document ใน Markdown ได้
`;
export const handler = async (event: Event, context: Context) => {
if (event.describe) {
return DOCS;
}
// รับค่าที่ส่งมา
const person = event.person || "";
const go = event.go || false;
return `
<div>
ส่งเมื่อคลิกโดยใช้แท็ก a และ cwdb-action
แสดงข้อความที่สร้างจากค่าที่ส่งมา
</div>
`;
};
เนื่องจาก Code มีความยาว จึงเขียนฉบับเต็มซ่อนไว้ที่ด้านล่างนี้
คลิกขยายเพื่อดู Code
type Event = {
describe: boolean;
widgetContext: any;
person: string;
go: boolean;
};
type Context = {
invokedFunctionArn: string;
};
const DOCS = `
## custom-widget-function
สามารถเขียน Document ใน Markdown ได้ เช่น **ตัวหนา**, *ตัวเอียง*, ~~ขีดทับ~~, \`Inline code\` เป็นต้น
* รายการ
* รายการ
* รายการย่อย
* รายการ
1. รายการลำดับหมายเลข
2. รายการลำดับหมายเลข
1. รายการลำดับหมายเลขย่อย
3. รายการลำดับหมายเลข
[ลิงก์](https://dev.classmethod.jp/author/tinnakorn-maneewong/)
![Image](https://raw.githubusercontent.com/eetann/choomame/main/public/icons/icon-128x128.png)
---
\`\`\` python
print("piyopiyo")
\`\`\`
### Table ก็สามารถเขียนได้
name | ok
---|---
Kerry | true
Johnny | false
Goro | false
| name | ok |
|---|---|
| Kerry | true |
| Johnny | false |
| Goro | false |
`;
export const handler = async (event: Event, context: Context) => {
if (event.describe) {
return DOCS;
}
const timestamp = new Date();
const person = event.person || "";
const go = event.go || false;
let response = "";
const people = [
{ name: "Kerry", ok: true, cls: "btn kerry" },
{ name: "Johnny", ok: false, cls: "btn btn-primary" },
{ name: "Goro", ok: false, cls: "btn" },
];
const rows = people
.map(({ name, ok, cls }) => {
return `
<tr>
<td>${name}</td>
<td>
<a class="${cls}">เลือก</a>
<cwdb-action
action="call"
endpoint="${context.invokedFunctionArn}"
confirmation="คุณจะเลือก ${name} จริงไหม?">
{ "person": "${name}", "go": ${ok} }
</cwdb-action>
</td>
</td>
</tr>
`;
// });
})
.join("");
if (person) {
if (go) {
response = `${person} "ถ้าถึงแล้วจะบอก"`;
} else {
response = `${person} "ถ้าว่างก็จะไป"`;
}
}
const _event = event;
_event.widgetContext.accountId = 123456789012;
return `
<div>
<table>
${rows}
</table>
<p>${timestamp}</p>
<p>${response}</p>
<pre>${JSON.stringify(_event, null, 2)}</pre>
</div>
<style>
.kerry {
color: white !important;
background-color: black !important;
}
</style>
`;
};
Deploy CDK ไปยัง Lambda
ก่อนทำการ Deploy CDK ไปยัง Lambda ให้ตรวจสอบไฟล์ lambda/custom-widget-function.ts อีกครั้งเพื่อให้มั่นใจว่าเราแก้ไขไฟล์ดังกล่าวไปแล้ว
เมื่อมั่นใจว่าแก้ไขไฟล์ lambda/custom-widget-function.ts
เสร็จแล้ว ให้ทำการ Deploy โปรเจกต์ตามลิงก์ด้านล่างนี้ดังที่กล่าวไว้ในหมายเหตุตอนแรก
เมื่อ Deploy โปรเจกต์เสร็จแล้ว Lambda จะถูกสร้างขึ้นแบบนี้ครับ
เตรียม Dashboard และสร้าง Custom widget ใน CloudWatch
เตรียม Dashboard ใน CloudWatch
ก่อนอื่นเราต้องมี Dashboard ใน CloudWatch ก่อนครับ
ครั้งนี้ผมจะใช้ Dashboard ชื่อว่า custom-widget-lambda-samble
ไปที่ช่องค้นหา พิมพ์ชื่อบริการ CloudWatch
แล้วเลือก CloudWatch
คลิก Dashboards
จากเมนูด้านซ้าย
คลิก Create dashboard
ปุ่มไหนก็ได้
ป้อนชื่อลงใน "Dashboard name" ตามต้องการ เช่น custom-widget-lambda-samble
แล้วคลิก Create dashboard
สร้าง Custom widget
เมื่อสร้างเสร็จแล้ว โดยปกติจะแสดงเป็นหน้าจอเริ่มต้นแบบนี้ (หากไม่แสดงหน้าจอแบบนี้ให้คลิก +
ด้านขวาบน)
แล้วเลือก Custom widget
แล้วคลิก Next
แล้วทำการตั้งค่าดังนี้
Lambda function: CustomWidgetLambdaSampleS-CustomWidgetLambdaxxxxxx-xxxxxxxxxxxx
(เลือกชื่อโปรเจกต์ที่ Deploy ในตอนแรก)
Documentation: Get documentation
(เมื่อคลิกแล้ว Document จะแสดงตามภาพ)
Preview: Preview widget
(เมื่อคลิกแล้ว Preview จะแสดงตามภาพ)
Widget title: นี่คือหัวข้อ
(ป้อนชื่อหัวข้อตามต้องการ)
เกี่ยวกับ Code
ผมจะแบ่งอธิบาย Code แยกทีละส่วนครับ
ก่อนอื่น ในส่วนของ if แรกที่ใช้ event.describe คือการแสดง Document
if (event.describe) { return DOCS; }
Document สามารถเขียนด้วย Markdown และแสดงเมื่อเพิ่มหรือแก้ไข Custom widget ได้
เมื่อได้ลองเขียน Document ดูจริงๆ นอกจากการตกแต่งข้อความและรายการต่างๆ แล้ว ยังสามารถแสดงภาพทั่วไปได้
ถ้าทำการตั้งค่าและตรวจสอบเสร็จแล้ว ให้คลิก Add widget
ได้เลย
ต่อไป เป็นการแทนที่ person
และ go
เป็นตัวแปรจาก event
argument แรกของ Lambda handler
และ event.person
และ event.go
จะใส่ค่าที่ส่งไปโดยใช้แท็ก cwdb-action
ของ HTML
const person = event.person || ""; const go = event.go || false;
แท็ก cwdb-action
เป็นตัวกำหนดการกระทำเมื่อเลือกตัวเลือก (Kerry, Johnny, Goro)
ในตัวอย่างครั้งนี้ จะเกิด Action ก็ต่อเมื่อทำการคลิกปุ่มตัวเลือกของแท็ก a
"การเรียก Lambda (=Lambda เดียวกัน) ที่แสดงใน Custom widget" ซึ่งหน้าที่จริงๆ คือการส่งค่าที่เขียนในแท็ก cwdb-action
<a class="${cls}">เลือก</a>
<cwdb-action
action="call"
endpoint="${context.invokedFunctionArn}"
confirmation="คุณจะเลือก ${name} จริงไหม?">
{ "person": "${name}", "go": ${ok} }
</cwdb-action>
จึงทำให้หน้าจอ Confirmation ปรากฏขึ้นมาโดยใช้ attribute confirmation
ผมได้กำหนดให้แสดงเป็นปุ่มใน class
ของแท็ก a
นอกจาก style btn
, btn-primary
ที่มีให้ใน Custom widget แล้ว เรายังสามารถตั้งค่า style ในแบบของเราได้ด้วยแท็ก style
ผมได้ลองแสดงเนื้อหาของ event
argument แรกของ Lambda handler โดยใช้แท็ก pre จึงได้รู้ว่ามี arguments ที่ 2 และ 3 ใน JSON.stringify()
<pre>${JSON.stringify(_event, null, 2)}</pre>
ด้านล่างนี้คือเนื้อหาที่แสดง นอกจาก person
และ go
ที่ส่งด้วยแท็ก cwdb-action
แล้ว ยังมีหลายค่าที่ใส่เข้าไปใน widgetContext
key และดูเหมือนว่าขนาดของ widget เป็นขนาดที่นิยมใช้กันอีกด้วย
{
"person": "Kerry",
"go": true,
"widgetContext": {
"dashboardName": "custom-widget-lambda-sample",
"widgetId": "widget-1",
"domain": "https://ap-northeast-1.console.aws.amazon.com",
"accountId": 123456789012,
"locale": "ja",
"timezone": {
"label": "Local",
"offsetISO": "+09:00",
"offsetInMinutes": -540
},
"period": 300,
"isAutoPeriod": true,
"timeRange": {
"mode": "relative",
"start": 1668725221893,
"end": 1668736021893,
"relativeStart": 10800004
},
"theme": "light",
"linkCharts": true,
"title": "นี่คือหัวข้อ",
"params": null,
"forms": {
"all": {}
},
"width": 1363,
"height": 764
}
}
จัดการกับ Comma ปริศนาที่แสดงขึ้นมา
ผมได้สร้าง character string ที่มีแท็กต่างๆ เช่น tr จากรายการตามด้านล่างนี้เพื่อแสดง Table
const rows = people
.map(({ name, ok, cls }) => {
return `
<tr>
<td>${name}</td>
<td>
<a class="${cls}">เลือก</a>
<cwdb-action
action="call"
endpoint="${context.invokedFunctionArn}"
confirmation="คุณจะเลือก ${name} จริงไหม?">
{ "person": "${name}", "go": ${ok} }
</cwdb-action>
</td>
</td>
</tr>
`;
// });
})
.join("");
หากลืมเชื่อมต่อด้วย .join("")
ที่อยู่บรรทัดสุดท้ายนี้ ก็จะมี Comma แสดงที่หน้า Table ดังนั้นโปรดอย่าลืมเชื่อมต่อไว้ครับ
เวลาการอัปเดต Custom widget
การอัปเดต Custom widget ไม่ใช่เพียงแค่ตอนที่โหลดหน้าเว็บหรือกดปุ่มรีเฟรชเพื่ออัปเดตด้วยตนเองเท่านั้น แต่เราสามารถควบคุมได้ว่าจะอัปเดตตามเวลาที่เกิดการกระทำจาก 3 ตัวเลือกของ Update on(Refresh, Resize, Time Range) หรือไม่ และการควบคุมเวลาของการอัปเดตสามารตั้งค่าในขณะที่สร้างหรือแก้ไข Custom widget ได้
เมื่อกดปุ่มรีเฟรชอัตโนมัติบน Dashboard
Custom widget สามารถควบคุมได้ว่าจะอัปเดตเมื่อกดปุ่มรีเฟรชอัตโนมัติบน Dashboard หรือไม่
เมื่อปรับขนาด Widget
Custom widget สามารถควบคุมได้ว่าจะอัปเดตเมื่อปรับขนาดของ widget หรือไม่
ดูเหมือนว่าขนาดของ widget จะรวมอยู่ใน width
และ height
ของ widgetContext
ของ event
argument แรกของ Lambda handler
เมื่อเปลี่ยนช่วงเวลา Dashboard
Custom widget สามารถควบคุมได้ว่าจะอัปเดตเมื่อเปลี่ยนช่วงเวลา Dashboard หรือไม่
ดูเหมือนว่าช่วงเวลาของ dashboard จะรวมอยู่ใน widgetContext.timeRange
ของ event
argument แรกของ Lambda handler
อย่าลืม Save Dashboard
เมื่อสร้าง Custom widget เสร็จแล้ว ให้บันทึกโดยกดปุ่ม Save
ที่แสดงอยู่ด้านขวาบนของ dashboard และหากทำการแก้ไขก็ต้องกดปุ่ม Save ทุกครั้ง แม้แต่ทางผู้เขียนเองก็ลืมค่อนข้างบ่อย ดังนั้นอย่าลืมที่จะ Save นะครับ
นอกจากนี้หากไม่ต้องการกดปุ่ม Save
ด้วยตัวเอง สามารถเปิดใช้งานเป็น Autosave: On
ได้ครับ
เมื่อลองแก้ไขบางอย่างแล้วจะแสดง ✅ Saved
แบบนี้
แต่ข้อเสียคือเมื่อมีการรีโหลดหน้าจอ จะทำให้สถานะกลับมาเป็น Autosave: Off
ดังนั้นให้ระวังในส่วนนี้ด้วยครับ
การลบ AWS Resource
การลบ CloudWatch Dashboard
Service name | Function name |
---|---|
CloudWatch | Dashboard |
เข้าไปที่ Service "CloudWatch > Dashboards" ค้นหาและเลือกอันที่ต้องการลบ คลิก Delete
และยืนยันการลบตามคำแนะนำ
การลบ AWS Resource ที่ใช้สร้างและ Deploy CDK ไปยัง Lambda
สำหรับวิธีการลบ AWS Resource ที่ใช้สร้างและ Deploy CDK ไปยัง Lambda ตามที่แสดงในตารางทั้งหมด ดูที่ลิงก์ด้านล่างนี้
Service name | Function name |
---|---|
CloudFormation | Stacks |
Lambda | Functions |
EC2 | Instances |
IAM | Roles |
ดูวิธีการลบที่หัวข้อนี้: การลบ AWS Resource
Link
ดูโค้ดตัวอย่างได้ที่ลิงก์ GitHub ด้านล่างนี้ (ในโปรเจกต์มีข้อความบางส่วนเป็นภาษาญี่ปุ่น แนะนำให้แก้ไขเป็นภาษาไทยก่อน Deploy)
Link อ้างอิง
- JSONをconsole.logで整形して表示する(JSON.stringifyでインデント付き展開) - Qiita (ภาษาญี่ปุ่น)
- Create and work with widgets on CloudWatch dashboards
- aws-samples/cloudwatch-custom-widgets-samples
- การเพิ่ม Monitoring ของ EC2 Instance ไปยัง CloudWatch Dashboard
บทความต้นฉบับ
แปลโดย: POP จากบริษัท Classmethod (Thailand) ครับ !